package tech.yunqian.slim.project.service;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import com.alibaba.fastjson.JSONObject;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;
import tech.yunqian.slim.project.domain.Device;
import tech.yunqian.slim.project.domain.DeviceMonitorRuleTrackingItem;
import tech.yunqian.slim.project.domain.EventMonitorConditionOccurence;
import tech.yunqian.slim.project.domain.MonitorCondition;
import tech.yunqian.slim.project.domain.MonitorConditionTrackingItem;
import tech.yunqian.slim.project.domain.Alarm;
import tech.yunqian.slim.project.domain.AlarmDetail;
import tech.yunqian.slim.project.domain.AlarmRule;
import tech.yunqian.slim.project.domain.MonitorRule;
import tech.yunqian.slim.project.domain.MonitorRuleTrackingItem;
import tech.yunqian.slim.project.domain.ScenarioAction;
import tech.yunqian.slim.project.domain.ScenarioActionJournal;
import tech.yunqian.slim.project.domain.ScenarioRule;
import tech.yunqian.slim.project.domain.TelemetryMonitorConditionOccurence;
import tech.yunqian.slim.project.enums.ActionType;
import tech.yunqian.slim.project.enums.MonitorRuleType;
import tech.yunqian.slim.project.enums.NotificationMethod;
import tech.yunqian.slim.project.enums.Severity;
import tech.yunqian.slim.project.enums.TriggerSource;
import tech.yunqian.slim.project.mapper.MonitorConditionMapper;
import tech.yunqian.slim.project.mapper.ScenarioActionJournalMapper;
import tech.yunqian.slim.project.mapper.ScenarioActionMapper;
import tech.yunqian.slim.project.mapper.AlarmDetailMapper;
import tech.yunqian.slim.project.mapper.AlarmMapper;
import tech.yunqian.slim.project.mapper.AlarmReceiverMapper;

@Slf4j
@Service
public class MonitorTrackerService {
    @Value("${notification.sms.templateCode:SMS_224136072}")
    private String smsTemplateCode;

    @Autowired
    private MonitorConditionMapper monitorConditionMapper;

    @Autowired
    private AlarmMapper alarmMapper;

    @Autowired
    private AlarmDetailMapper alarmDetailMapper;

    @Autowired
    private ScenarioActionMapper scenarioActionMapper;

    @Autowired
    private ScenarioActionJournalMapper scenarioActionJournalMapper;

    @Autowired
    private AlarmReceiverMapper alarmReceiverMapper;

    // @Autowired
    // private SmsConfigurationMapper smsConfigurationMapper;

    // @Autowired
    // private SmsTemplateMapper smsTemplateMapper;

    @Autowired
    private SpecificFunctionService specificFunctionService;

    @Autowired
    private NotificationService notificationService;

    private static Map<Integer, DeviceMonitorRuleTrackingItem> productMonitorTracker;
    private static Map<Integer, MonitorRuleTrackingItem> deviceMonitorTracker;

    // TODO: [zy] transation
    private Boolean openAlarm(MonitorRuleType type, Integer deviceId, AlarmRule alarmRule) {
        Boolean done = false;
        try {
            Integer monitorRuleId = alarmRule.getMonitorRuleId();
            Integer projectId = alarmRule.getProjectId();
            MonitorRuleTrackingItem monitorRuleTrackingItem = null;
            if (type.equals(MonitorRuleType.PRODUCT)) {
                DeviceMonitorRuleTrackingItem deviceMonitorRuleTrackingItem = productMonitorTracker.get(deviceId);
                monitorRuleTrackingItem = deviceMonitorRuleTrackingItem.getItem(monitorRuleId);
            } else if (type.equals(MonitorRuleType.DEVICE)) {
                monitorRuleTrackingItem = deviceMonitorTracker.get(monitorRuleId);
            }
            Alarm alarm = monitorRuleTrackingItem.getAlarm();
            if (alarm == null) {
                alarm = new Alarm();
                alarm.setProjectId(projectId);
                alarm.setRuleId(monitorRuleId);
                alarm.setStart(new Date());
                Integer affected = alarmMapper.create(alarm);
                if (affected == null) {
                    throw new Exception("alarm not created");
                }
                done = true;
                monitorRuleTrackingItem.setAlarm(alarm);
                Map<Integer, MonitorConditionTrackingItem> monitorConditionTrackingItemMap = monitorRuleTrackingItem.getMonitorConditionTrackingItemMap();
                for (Map.Entry<Integer, MonitorConditionTrackingItem> entry : monitorConditionTrackingItemMap.entrySet()) {
                    MonitorConditionTrackingItem monitorConditionTrackingItem = entry.getValue();
                    AlarmDetail alarmDetail = monitorConditionTrackingItem.getAlarmDetail();
                    if (alarmDetail != null) {
                        alarmDetail.setAlarmId(alarm.getId());
                        affected = alarmDetailMapper.create(alarmDetail);
                        if (affected == null) {
                            continue;
                        }
                    } else {
                        continue;
                    }
                }
            }
        } catch (Exception e) {
            log.info(e.getMessage());
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
        }
        return done;
    }

    private Boolean openProductAlarm(Integer deviceId, AlarmRule alarmRule) {
        return openAlarm(MonitorRuleType.PRODUCT, deviceId, alarmRule);
    }

    private Boolean openDeviceAlarm(Integer deviceId, AlarmRule alarmRule) {
        return openAlarm(MonitorRuleType.DEVICE, deviceId, alarmRule);
    }

    private Boolean openAlarmDetail(MonitorRuleType type, Integer deviceId, MonitorCondition monitorCondition) {
        Boolean done = false;
        try {
            Integer monitorRuleId = monitorCondition.getRuleId();
            Integer monitorConditionId = monitorCondition.getId();
            MonitorRuleTrackingItem monitorRuleTrackingItem = null;
            if (type.equals(MonitorRuleType.PRODUCT)) {
                DeviceMonitorRuleTrackingItem deviceMonitorRuleTrackingItem = productMonitorTracker.get(deviceId);
                monitorRuleTrackingItem = deviceMonitorRuleTrackingItem.getItem(monitorRuleId);
            } else if (type.equals(MonitorRuleType.DEVICE)) {
                monitorRuleTrackingItem = deviceMonitorTracker.get(monitorRuleId);
            }
            Alarm alarm = monitorRuleTrackingItem.getAlarm();
            Integer alarmId = null;
            if (alarm != null) {
                alarmId = alarm.getId();
            }
            MonitorConditionTrackingItem monitorConditionTrackingItem = monitorRuleTrackingItem.getItem(monitorConditionId);
            AlarmDetail alarmDetail = new AlarmDetail();
            Integer triggerSource = monitorCondition.getTriggerSource();
            if (triggerSource.equals(TriggerSource.TELEMETRY.getCode())) {
                List<TelemetryMonitorConditionOccurence> telemetryMonitorConditionOccurences = monitorConditionTrackingItem.getTelemetryMonitorConditionOccurences();
                TelemetryMonitorConditionOccurence telemetryMonitorConditionOccurence = telemetryMonitorConditionOccurences.get(0);
                alarmDetail.setValue(telemetryMonitorConditionOccurence.getValue());
                alarmDetail.setStart(telemetryMonitorConditionOccurence.getTimestamp());
            } else if (triggerSource.equals(TriggerSource.EVENT.getCode())) {
                List<EventMonitorConditionOccurence> eventMonitorConditionOccurences = monitorConditionTrackingItem.getEventMonitorConditionOccurences();
                EventMonitorConditionOccurence eventMonitorConditionOccurence = eventMonitorConditionOccurences.get(0);
                alarmDetail.setValue(eventMonitorConditionOccurence.getParams());
                alarmDetail.setStart(eventMonitorConditionOccurence.getTimestamp());
                alarmDetail.setEnd(eventMonitorConditionOccurence.getTimestamp());
            }
            alarmDetail.setAlarmId(alarmId);
            alarmDetail.setConditionId(monitorConditionId);
            alarmDetail.setDeviceId(deviceId);
            if (alarmId != null) {
                Integer affected = alarmDetailMapper.create(alarmDetail);
                if (affected == null) {
                    throw new Exception("alarm detail not created");
                }
            }
            monitorConditionTrackingItem.setAlarmDetail(alarmDetail);
            if (type.equals(MonitorRuleType.PRODUCT)) {
                DeviceMonitorRuleTrackingItem deviceMonitorRuleTrackingItem = productMonitorTracker.get(deviceId);
                productMonitorTracker.put(deviceId, deviceMonitorRuleTrackingItem);
            } else if (type.equals(MonitorRuleType.DEVICE)) {
                deviceMonitorTracker.put(monitorRuleId, monitorRuleTrackingItem);
            } 
            done = true;
        } catch (Exception e) {
            log.info(e.getMessage());
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
        }
        return done;
    }

    private Boolean openProductAlarmDetail(Integer deviceId, MonitorCondition monitorCondition) {
        return openAlarmDetail(MonitorRuleType.PRODUCT, deviceId, monitorCondition);
    }

    private Boolean openDeviceAlarmDetail(Integer deviceId, MonitorCondition monitorCondition) {
        return openAlarmDetail(MonitorRuleType.DEVICE, deviceId, monitorCondition);
    }

    private Boolean closeAlarm(MonitorRuleType type, Integer deviceId, Integer monitorRuleId) {
        Boolean done = false;
        try {
            MonitorRuleTrackingItem monitorRuleTrackingItem = null;
            if (type.equals(MonitorRuleType.PRODUCT)) {
                DeviceMonitorRuleTrackingItem deviceMonitorRuleTrackingItem = productMonitorTracker.get(deviceId);
                monitorRuleTrackingItem = deviceMonitorRuleTrackingItem.getItem(monitorRuleId);
            } else if (type.equals(MonitorRuleType.DEVICE)) {
                monitorRuleTrackingItem = deviceMonitorTracker.get(monitorRuleId);
            }
            Alarm alarm = monitorRuleTrackingItem.getAlarm();
            Integer alarmId = alarm.getId();
            Map<String, Object> fields = new HashMap<>();
            fields.put("end", new Date());
            alarmMapper.update(alarmId, fields);
            monitorRuleTrackingItem.setAlarm(null);
            done = true;
        } catch (Exception e) {
            log.info(e.getMessage());
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
        }
        return done;
    }

    private Boolean closeProductAlarm(Integer deviceId, Integer monitorRuleId) {
        return closeAlarm(MonitorRuleType.PRODUCT, deviceId, monitorRuleId);
    }

    private Boolean closeDeviceAlarm(Integer deviceId, Integer monitorRuleId) {
        return closeAlarm(MonitorRuleType.DEVICE, deviceId, monitorRuleId);
    }

    private Boolean closeAlarmDetail(MonitorRuleType type, Integer deviceId, Integer monitorRuleId, Integer monitorConditionId) {
        Boolean done = false;
        try {
            MonitorRuleTrackingItem monitorRuleTrackingItem = null;
            if (type.equals(MonitorRuleType.PRODUCT)) {
                DeviceMonitorRuleTrackingItem deviceMonitorRuleTrackingItem = productMonitorTracker.get(deviceId);
                monitorRuleTrackingItem = deviceMonitorRuleTrackingItem.getItem(monitorRuleId);
            } else if (type.equals(MonitorRuleType.DEVICE)) {
                monitorRuleTrackingItem = deviceMonitorTracker.get(monitorRuleId);
            }
            MonitorConditionTrackingItem monitorConditionTrackingItem = monitorRuleTrackingItem.getItem(monitorConditionId);
            AlarmDetail alarmDetail = monitorConditionTrackingItem.getAlarmDetail();
            Integer detailId = alarmDetail.getId();
            if (detailId != null) {
                Map<String, Object> fields = new HashMap<>();
                fields.put("end", new Date());
                alarmDetailMapper.update(detailId, fields);
            }
            monitorConditionTrackingItem.setAlarmDetail(null);
            done = true;
        } catch (Exception e) {
            log.info(e.getMessage());
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
        }
        return done;
    }

    private Boolean closeProductAlarmDetail(Integer deviceId, Integer monitorRuleId, Integer monitorConditionId) {
        return closeAlarmDetail(MonitorRuleType.PRODUCT, deviceId, monitorRuleId, monitorConditionId);
    }

    private Boolean closeDeviceAlarmDetail(Integer deviceId, Integer monitorRuleId, Integer monitorConditionId) {
        return closeAlarmDetail(MonitorRuleType.DEVICE, deviceId, monitorRuleId, monitorConditionId);
    }

    private Boolean fireAction(MonitorRuleType type, Integer deviceId, ScenarioRule scenarioRule) {
        Boolean done = false;
        try {
            Integer scenarioRuleId = scenarioRule.getId();
            Map<String, Object> filters = new HashMap<>();
            filters.put("rule_id", scenarioRuleId);
            List<ScenarioAction> scenarioActions = scenarioActionMapper.lookup(filters);
            for (ScenarioAction scenarioAction : scenarioActions) {
                Integer actionType = scenarioAction.getType();
                if (ActionType.valueOfCode(actionType).equals(ActionType.SPECIFIC_FUNCTION)) {
                    String sfCode = scenarioAction.getSfCode();
                    JSONObject data = new JSONObject();
                    if (type == MonitorRuleType.PRODUCT) {
                        data.put("deviceId", deviceId);
                    }
                    data.put("scenarioActionId", scenarioAction.getId());
                    JSONObject sfParameters = null;
                    try {
                        sfParameters = JSONObject.parseObject(scenarioAction.getSfParameters());
                    } catch (Exception e) {}
                    String sfToken = scenarioAction.getSfToken();
                    JSONObject request = new JSONObject();
                    request.put("code", sfCode);
                    request.put("data", data);
                    request.put("params", sfParameters);
                    request.put("token", sfToken);
                    JSONObject result = specificFunctionService.call(request);
                    ScenarioActionJournal scenarioActionJournal = new ScenarioActionJournal();
                    scenarioActionJournal.setDeviceId(deviceId);
                    scenarioActionJournal.setScenarioActionId(scenarioAction.getId());
                    scenarioActionJournal.setData(data.toJSONString());
                    scenarioActionJournal.setResult(result.toJSONString());
                    Integer state = 0;
                    Integer code = result.getInteger("code");
                    if (code == 200) {
                        state = 1;
                    }
                    scenarioActionJournal.setState(state);
                    scenarioActionJournal.setTimestamp(new Date());
                    Integer affected = scenarioActionJournalMapper.create(scenarioActionJournal);
                    if (affected > 0) {
                        done = true;
                    }
                } else if (ActionType.valueOfCode(actionType).equals(ActionType.API)) {
                    done = true;
                } else if (ActionType.valueOfCode(actionType).equals(ActionType.RPC)) {
                    done = true;
                }
            }
        } catch (Exception e) {
            log.info(e.getMessage());
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
        }
        return done;
    }

    private Boolean fireProductAction(Integer deviceId, ScenarioRule scenarioRule) {
        return fireAction(MonitorRuleType.PRODUCT, deviceId, scenarioRule);
    }

    private Boolean fireDeviceAction(Integer deviceId, ScenarioRule scenarioRule) {
        return fireAction(MonitorRuleType.DEVICE, deviceId, scenarioRule);
    }

    private Boolean sendNotification(MonitorRuleType type, Integer deviceId, AlarmRule alarmRule) {
        Boolean done = false;
        try {
            Integer monitorRuleId = alarmRule.getMonitorRuleId();
            MonitorRuleTrackingItem monitorRuleTrackingItem = null;
            Set<String> deviceNames = new TreeSet<>();
            if (type.equals(MonitorRuleType.PRODUCT)) {
                DeviceMonitorRuleTrackingItem deviceMonitorRuleTrackingItem = productMonitorTracker.get(deviceId);
                Device device = deviceMonitorRuleTrackingItem.getDevice();
                deviceNames.add(device.getName());
                monitorRuleTrackingItem = deviceMonitorRuleTrackingItem.getItem(monitorRuleId);
            } else if (type.equals(MonitorRuleType.DEVICE)) {
                monitorRuleTrackingItem = deviceMonitorTracker.get(monitorRuleId);
                for (Integer id : monitorRuleTrackingItem.ids()) {
                    MonitorConditionTrackingItem monitorConditionTrackingItem = monitorRuleTrackingItem.getItem(id);
                    deviceNames.add(monitorConditionTrackingItem.getDeviceName());
                }
            }
            Long companyId = alarmRule.getCompanyId();
            Alarm alarm = monitorRuleTrackingItem.getAlarm();
            MonitorRule monitorRule = monitorRuleTrackingItem.getMonitorRule();
            Map<String, Object> filters = new HashMap<>();
            filters.put("company_id", companyId);
            filters.clear();
            filters.put("rule_id", alarmRule.getId());
            List<Map<String, Object>> receivers = alarmReceiverMapper.get(filters);
            List<String> smsNumbers = new ArrayList<>();
            List<String> mailAddress = new ArrayList<>();
            List<String> voiceNumbers = new ArrayList<>();
            List<String> wechatOpenIds = new ArrayList<>();
            for (Map<String, Object> receiver: receivers) {
                Integer method =  Integer.parseInt(receiver.get("method").toString());
                if (method.equals(NotificationMethod.SMS.getCode())) {
                    String phoneNumber = (String)receiver.get("phonenumber");
                    if (phoneNumber != null && !phoneNumber.isEmpty()) {
                        smsNumbers.add(phoneNumber);
                    }
                } else if (method.equals(NotificationMethod.EMAIL.getCode())) {
                    String email = (String)receiver.get("email");
                    if (email != null && !email.isEmpty()) {
                        mailAddress.add(email);
                    }
                } else if (method.equals(NotificationMethod.VOICE.getCode())) {
                    String phoneNumber = (String)receiver.get("phonenumber");
                    if (phoneNumber != null && !phoneNumber.isEmpty()) {
                        voiceNumbers.add(phoneNumber);
                    }
                } else if (method.equals(NotificationMethod.WECHAT.getCode())) {
                    String openId = (String)receiver.get("open_id");
                    if (openId != null && !openId.isEmpty()) {
                        wechatOpenIds.add(openId);
                    }
                }
            }
            try {
                if (smsNumbers.isEmpty()) {
                    log.info("no valid phone number");
                } else {
                    JSONObject request = new JSONObject();
                    request.put("companyId", companyId);
                    request.put("receivers", String.join(",", smsNumbers));
                    request.put("type", "alarm");
                    JSONObject params = new JSONObject();
                    params.put("project", alarmRule.getProjectName());
                    params.put("names", String.join(",", deviceNames));
                    params.put("rule", monitorRule.getName());
                    params.put("alarmId", alarm.getId());
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    params.put("time", sdf.format(alarm.getStart()));
                    params.put("severity", Severity.valueOfCode(alarmRule.getSeverity()).getLabel());
                    request.put("parameters", params);
                    log.info("****** {}", request);
                    notificationService.sendSms(request);
                }
            } catch (Exception e) {
                log.info(e.getMessage());
                StackTraceElement[] traces = e.getStackTrace();
                for (StackTraceElement trace : traces) {
                    log.debug(trace.toString());
                }
            }
            try {
                if (mailAddress.isEmpty()) {
                    log.info("no valid mail address");
                } else {
                    JSONObject request = new JSONObject();
                    request.put("companyId", companyId);
                    request.put("receivers", mailAddress);
                    request.put("type", "alarm");
                    request.put("subject", "物联平台 - 报警通知");
                    JSONObject params = new JSONObject();
                    params.put("project", alarmRule.getProjectName());
                    params.put("names", String.join(",", deviceNames));
                    params.put("rule", monitorRule.getName());
                    params.put("alarmId", alarm.getId());
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    params.put("time", sdf.format(alarm.getStart()));
                    params.put("severity", Severity.valueOfCode(alarmRule.getSeverity()).getLabel());
                    request.put("parameters", params);
                    request.put("attachments", new ArrayList<>());
                    log.info("****** {}", request);
                    notificationService.sendEmail(request);
                }
            } catch (Exception e) {
                log.info(e.getMessage());
                StackTraceElement[] traces = e.getStackTrace();
                for (StackTraceElement trace : traces) {
                    log.debug(trace.toString());
                }
            }
            try {
                if (voiceNumbers.isEmpty()) {
                    log.info("no valid phone number");
                } else {
                    for (String receiver : voiceNumbers) {
                        JSONObject request = new JSONObject();
                        request.put("companyId", companyId);
                        request.put("receiver", receiver);
                        request.put("type", "alarm");
                        JSONObject params = new JSONObject();
                        params.put("project", alarmRule.getProjectName());
                        params.put("names", String.join(",", deviceNames));
                        params.put("rule", monitorRule.getName());
                        params.put("alarmId", alarm.getId());
                        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                        params.put("time", sdf.format(alarm.getStart()));
                        params.put("severity", Severity.valueOfCode(alarmRule.getSeverity()).getLabel());
                        request.put("parameters", params);
                        log.info("****** {}", request);
                        notificationService.sendVoice(request);
                    }
                }
            } catch (Exception e) {
                log.info(e.getMessage());
                StackTraceElement[] traces = e.getStackTrace();
                for (StackTraceElement trace : traces) {
                    log.debug(trace.toString());
                }
            }
            try {
                if (wechatOpenIds.isEmpty()) {
                    log.info("no valid wechat openid");
                } else {
                    JSONObject request = new JSONObject();
                    request.put("companyId", companyId);
                    request.put("receivers", wechatOpenIds);
                    request.put("type", "alarm");
                    JSONObject params = new JSONObject();
                    params.put("project", alarmRule.getProjectName());
                    params.put("names", String.join(",", deviceNames));
                    params.put("rule", monitorRule.getName());
                    params.put("alarmId", alarm.getId());
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    params.put("time", sdf.format(alarm.getStart()));
                    params.put("severity", Severity.valueOfCode(alarmRule.getSeverity()).getLabel());
                    request.put("parameters", params);
                    log.info("****** {}", request);
                    notificationService.sendWechat(request);
                }
            } catch (Exception e) {
                log.info(e.getMessage());
                StackTraceElement[] traces = e.getStackTrace();
                for (StackTraceElement trace : traces) {
                    log.debug(trace.toString());
                }
            }
        } catch (Exception e) {
            log.info(e.getMessage());
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
        }
        return done;
    }

    public void raiseProduct(Map<String, Object> info) {
        if (productMonitorTracker == null) {
            productMonitorTracker = new HashMap<>();
        }
        Device device = (Device)info.get("device");
        Integer deviceId = device.getId();
        MonitorRule monitorRule = (MonitorRule)info.get("monitorRule");
        Integer monitorRuleId = monitorRule.getId();
        MonitorCondition monitorCondition = (MonitorCondition)info.get("monitorCondition");
        Integer monitorConditionId = monitorCondition.getId();
        Integer triggerSource = monitorCondition.getTriggerSource();
        TelemetryMonitorConditionOccurence telemetryMonitorConditionOccurence = null;
        EventMonitorConditionOccurence eventMonitorConditionOccurence = null;
        if (triggerSource.equals(TriggerSource.TELEMETRY.getCode())) {
            String value = (String)info.get("value");
            Date timestamp = (Date)info.get("timestamp");
            telemetryMonitorConditionOccurence = new TelemetryMonitorConditionOccurence(value, timestamp);
        } else if (triggerSource.equals(TriggerSource.EVENT.getCode())) {
            String params = (String)info.get("params");
            Date timestamp = (Date)info.get("timestamp");
            eventMonitorConditionOccurence = new EventMonitorConditionOccurence(params, timestamp);
        }
        DeviceMonitorRuleTrackingItem deviceMonitorRuleTrackingItem = null;
        MonitorRuleTrackingItem monitorRuleTrackingItem = null;
        MonitorConditionTrackingItem monitorConditionTrackingItem = null;
        // TODO: [zy] check if the rule/condition was changed
        if (!productMonitorTracker.containsKey(deviceId)) {
            deviceMonitorRuleTrackingItem = new DeviceMonitorRuleTrackingItem(device);
            productMonitorTracker.put(deviceId, deviceMonitorRuleTrackingItem);
        } else {
            deviceMonitorRuleTrackingItem = productMonitorTracker.get(deviceId);
        }
        if (!deviceMonitorRuleTrackingItem.contains(monitorRuleId)) {
            monitorRuleTrackingItem = new MonitorRuleTrackingItem(monitorRule);
            deviceMonitorRuleTrackingItem.putItem(monitorRuleId, monitorRuleTrackingItem);
        } else {
            monitorRuleTrackingItem = deviceMonitorRuleTrackingItem.getItem(monitorRuleId);
        }
        Boolean ruleIsOnBefore = monitorRuleTrackingItem.isFulfilled();
        List<MonitorCondition> monitorConditions = monitorConditionMapper.indexByRuleId(monitorRuleId);
        log.info("==> 1: {}", monitorConditions);
        Set<Integer> validConditionIds = new TreeSet<>();
        for (MonitorCondition sibling : monitorConditions) {
            log.info("==> 2: {}", sibling);
            Integer siblingId = sibling.getId();
            validConditionIds.add(siblingId);
            if (!monitorRuleTrackingItem.contains(siblingId)) {
                log.info("==> 3: {}", monitorRuleTrackingItem);
                monitorConditionTrackingItem = new MonitorConditionTrackingItem(device.getName(), sibling);
                monitorRuleTrackingItem.putItem(siblingId, monitorConditionTrackingItem);
            } else {
                log.info("==> 4: {}", monitorRuleTrackingItem);
                monitorConditionTrackingItem = monitorRuleTrackingItem.getItem(siblingId);
            }
            log.info("==> 5: {}", monitorConditionId);
            if (sibling.getId().equals(monitorConditionId)) {
                log.info("==> 6: {}", monitorConditionId);
                Boolean conditionIsOnBefore = monitorConditionTrackingItem.isFulfilled();
                if (telemetryMonitorConditionOccurence != null) {
                    monitorConditionTrackingItem.appendTelemetryMonitorConditionOccurence(telemetryMonitorConditionOccurence);
                }
                if (eventMonitorConditionOccurence != null) {
                    monitorConditionTrackingItem.appendEventMonitorConditionOccurence(eventMonitorConditionOccurence);
                }
                Boolean conditionIsOnAfter = monitorConditionTrackingItem.isFulfilled();
                log.info("==> 7: {} {}", conditionIsOnBefore, conditionIsOnAfter);
                if (conditionIsOnBefore != conditionIsOnAfter) {
                    openProductAlarmDetail(deviceId, monitorCondition);
                }
            }
        }
        for (Integer conditionId : monitorRuleTrackingItem.ids()) {
            if (!validConditionIds.contains(conditionId)) {
                log.info("######## condition out of date: {}", monitorRuleTrackingItem.getItem(conditionId));
                monitorRuleTrackingItem.removeItem(conditionId);
            }
        }
        Boolean ruleIsOnAfter = monitorRuleTrackingItem.isFulfilled();
        log.info("==> 8: {} {}", ruleIsOnBefore, ruleIsOnAfter);
        if (ruleIsOnBefore != ruleIsOnAfter) {
            List<AlarmRule> alarmRules = monitorRule.getAlarmRules();
            log.info("==> 9: {}", alarmRules);
            if (alarmRules != null && !alarmRules.isEmpty()) {
                for (AlarmRule alarmRule : alarmRules) {
                    log.info("==> 10: {}", alarmRule);
                    openProductAlarm(deviceId, alarmRule);
                    sendNotification(MonitorRuleType.PRODUCT, deviceId, alarmRule);
                }
            }
            List<ScenarioRule> scenarioRules = monitorRule.getScenarioRules();
            if (scenarioRules != null && !scenarioRules.isEmpty()) {
                log.info("fire: {} {}", device, monitorRule.getScenarioRules());
                for (ScenarioRule scenarioRule : scenarioRules) {
                    fireProductAction(deviceId, scenarioRule);
                }
            }
        }
    }

    public void raiseDevice(Map<String, Object> info) {
        if (deviceMonitorTracker == null) {
            deviceMonitorTracker = new HashMap<>();
        }
        Device device = (Device)info.get("device");
        Integer deviceId = device.getId();
        MonitorRule monitorRule = (MonitorRule)info.get("monitorRule");
        Integer monitorRuleId = monitorRule.getId();
        MonitorCondition monitorCondition = (MonitorCondition)info.get("monitorCondition");
        Integer monitorConditionId = monitorCondition.getId();
        Integer triggerSource = monitorCondition.getTriggerSource();
        TelemetryMonitorConditionOccurence telemetryMonitorConditionOccurence = null;
        EventMonitorConditionOccurence eventMonitorConditionOccurence = null;
        if (triggerSource.equals(TriggerSource.TELEMETRY.getCode())) {
            String value = (String)info.get("value");
            Date timestamp = (Date)info.get("timestamp");
            telemetryMonitorConditionOccurence = new TelemetryMonitorConditionOccurence(value, timestamp);
        } else if (triggerSource.equals(TriggerSource.EVENT.getCode())) {
            String params = (String)info.get("params");
            Date timestamp = (Date)info.get("timestamp");
            eventMonitorConditionOccurence = new EventMonitorConditionOccurence(params, timestamp);
        }
        MonitorRuleTrackingItem monitorRuleTrackingItem = null;
        MonitorConditionTrackingItem monitorConditionTrackingItem = null;
        if (!deviceMonitorTracker.containsKey(monitorRuleId)) {
            monitorRuleTrackingItem = new MonitorRuleTrackingItem(monitorRule);
            deviceMonitorTracker.put(monitorRuleId, monitorRuleTrackingItem);
        } else {
            monitorRuleTrackingItem = deviceMonitorTracker.get(monitorRuleId);
        }
        Boolean ruleIsOnBefore = monitorRuleTrackingItem.isFulfilled();
        List<MonitorCondition> monitorConditions = monitorConditionMapper.indexByRuleId(monitorRuleId);
        Set<Integer> validConditionIds = new TreeSet<>();
        for (MonitorCondition sibling : monitorConditions) {
            Integer siblingId = sibling.getId();
            validConditionIds.add(siblingId);
            if (!monitorRuleTrackingItem.contains(siblingId)) {
                monitorConditionTrackingItem = new MonitorConditionTrackingItem(device.getName(), sibling);
                monitorRuleTrackingItem.putItem(siblingId, monitorConditionTrackingItem);
            } else {
                monitorConditionTrackingItem = monitorRuleTrackingItem.getItem(siblingId);
            }
            if (sibling.getId().equals(monitorConditionId)) {
                Boolean conditionIsOnBefore = monitorConditionTrackingItem.isFulfilled();
                monitorConditionTrackingItem.appendTelemetryMonitorConditionOccurence(telemetryMonitorConditionOccurence);
                monitorConditionTrackingItem.appendEventMonitorConditionOccurence(eventMonitorConditionOccurence);
                Boolean conditionIsOnAfter = monitorConditionTrackingItem.isFulfilled();
                if (!conditionIsOnBefore && conditionIsOnAfter) {
                    openDeviceAlarmDetail(deviceId, monitorCondition);
                }
            }
        }
        for (Integer conditionId : monitorRuleTrackingItem.ids()) {
            if (!validConditionIds.contains(conditionId)) {
                log.info("######## condition out of date: {}", monitorRuleTrackingItem.getItem(conditionId));
                monitorRuleTrackingItem.removeItem(conditionId);
            }
        }
        Boolean ruleIsOnAfter = monitorRuleTrackingItem.isFulfilled();
        if (!ruleIsOnBefore && ruleIsOnAfter) {
            List<AlarmRule> alarmRules = monitorRule.getAlarmRules();
            if (alarmRules != null && !alarmRules.isEmpty()) {
                for (AlarmRule alarmRule : alarmRules) {
                    openDeviceAlarm(deviceId, alarmRule);
                    sendNotification(MonitorRuleType.DEVICE, deviceId, alarmRule);
                }
            }
            List<ScenarioRule> scenarioRules = monitorRule.getScenarioRules();
            if (scenarioRules != null && !scenarioRules.isEmpty()) {
                log.info("fire: {} {}", device, monitorRule.getScenarioRules());
                for (ScenarioRule scenarioRule : scenarioRules) {
                    fireDeviceAction(deviceId, scenarioRule);
                }
            }
        }
    }

    public void raise(Map<String, Object> info) {
        MonitorRule monitorRule = (MonitorRule)info.get("monitorRule");
        if (monitorRule.getType().equals(MonitorRuleType.PRODUCT.getCode())) {
            raiseProduct(info);
        } else if (monitorRule.getType().equals(MonitorRuleType.DEVICE.getCode())) {
            raiseDevice(info);
        }
    }

    public void dropProduct(Map<String, Object> info) {
        if (productMonitorTracker == null) {
            return;
        }
        Device device = (Device)info.get("device");
        Integer deviceId = device.getId();
        if (!productMonitorTracker.containsKey(deviceId)) {
            return;
        }
        DeviceMonitorRuleTrackingItem deviceMonitorRuleTrackingItem = productMonitorTracker.get(deviceId);
        MonitorRule monitorRule = (MonitorRule)info.get("monitorRule");
        Integer monitorRuleId = monitorRule.getId();
        if (!deviceMonitorRuleTrackingItem.contains(monitorRuleId)) {
            return;
        }
        MonitorRuleTrackingItem monitorRuleTrackingItem = deviceMonitorRuleTrackingItem.getItem(monitorRuleId);
        MonitorCondition monitorCondition = (MonitorCondition)info.get("monitorCondition");
        Integer monitorConditionId = monitorCondition.getId();
        if (!monitorRuleTrackingItem.contains(monitorConditionId)) {
            return;
        }
        MonitorConditionTrackingItem monitorConditionTrackingItem = monitorRuleTrackingItem.getItem(monitorConditionId);
        Boolean ruleIsOnBefore = monitorRuleTrackingItem.isFulfilled();
        Boolean conditionIsOnBefore = monitorConditionTrackingItem.isFulfilled();
        monitorConditionTrackingItem.clear();
        Boolean conditionIsOnAfter = monitorConditionTrackingItem.isFulfilled();
        Boolean ruleIsOnAfter = monitorRuleTrackingItem.isFulfilled();
        if (conditionIsOnBefore && !conditionIsOnAfter) {
            closeProductAlarmDetail(deviceId, monitorRuleId, monitorConditionId);
        }
        if (ruleIsOnBefore && !ruleIsOnAfter) {
            closeProductAlarm(deviceId, monitorRuleId);
        }
    }

    public void dropDevice(Map<String, Object> info) {
        log.info("drop device: {} {}", info, deviceMonitorTracker);
        if (deviceMonitorTracker == null) {
            return;
        }
        Device device = (Device)info.get("device");
        Integer deviceId = device.getId();
        MonitorRule monitorRule = (MonitorRule)info.get("monitorRule");
        Integer monitorRuleId = monitorRule.getId();
        if (!deviceMonitorTracker.containsKey(monitorRuleId)) {
            return;
        }
        MonitorRuleTrackingItem monitorRuleTrackingItem = deviceMonitorTracker.get(monitorRuleId);
        MonitorCondition monitorCondition = (MonitorCondition)info.get("monitorCondition");
        Integer monitorConditionId = monitorCondition.getId();
        if (!monitorRuleTrackingItem.contains(monitorConditionId)) {
            return;
        }
        MonitorConditionTrackingItem monitorConditionTrackingItem = monitorRuleTrackingItem.getItem(monitorConditionId);
        Boolean ruleIsOnBefore = monitorRuleTrackingItem.isFulfilled();
        Boolean conditionIsOnBefore = monitorConditionTrackingItem.isFulfilled();
        monitorConditionTrackingItem.clear();
        Boolean conditionIsOnAfter = monitorConditionTrackingItem.isFulfilled();
        Boolean ruleIsOnAfter = monitorRuleTrackingItem.isFulfilled();
        if (conditionIsOnBefore && !conditionIsOnAfter) {
            closeDeviceAlarmDetail(deviceId, monitorRuleId, monitorConditionId);
            log.info("close condition: {}", monitorConditionId);
        }
        if (ruleIsOnBefore && !ruleIsOnAfter) {
            closeDeviceAlarm(deviceId, monitorRuleId);
            log.info("close alarm: {}", monitorRuleId);
        }
    }

    public void drop(Map<String, Object> info) {
        MonitorRule monitorRule = (MonitorRule)info.get("monitorRule");
        if (monitorRule.getType().equals(MonitorRuleType.PRODUCT.getCode())) {
            dropProduct(info);
        } else if (monitorRule.getType().equals(MonitorRuleType.DEVICE.getCode())) {
            dropDevice(info);
        }
    }
}
